home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 52
/
Aminet 52 (2002)(GTI - Schatztruhe)[!][Dec 2002].iso
/
Aminet
/
docs
/
mags
/
saku18.lha
/
Teksti
/
Ohjelmointi.txt
< prev
next >
Wrap
Text File
|
1996-04-28
|
18KB
|
407 lines
2
1*
{A Järjestelmäohjelmoinnin alkeiskurssi - Osa 5: DOS
{A -------------------------------------------------
{8 Sami Klemola
Tällä kertaa teemme läpileikkauksen dos.libraryyn. Ensin kuitenkin pieni korjaus
kurssin edellisen osan tekstiin. Virhe ei ollut minun - tietenkään - minähän en
tunnetusti tee virheitä. Kyse on siitä, että puhuin olioperustaisista kirjas-
toista, ja oikolukija muutti sen oliopohjaiseksi tietämättä, että ne ovat kaksi
aivan eri asiaa, ja teki näin tekstiin asiavirheen. Oliopohjainen on sellainen
systeemi, joka toimii oliomuotoisesti vain tietyiltä osilta, kun taas Triton
toimii kokonaan olioiden voimalla eli se on olioperustainen. Pahoittelen sitä,
että oikolukija kajosi taas asiaan, johon hänen ei olisi pitänyt puuttua. Tästä
kuitenkin nyt asiaan.
{DDOS
Kun Amiga käynnistyy, käynnistyslevyltä ladataan koodi, joka alustaa dos.libra-
ryn. Se jatkaa prosessia muodostamalla tiedostojärjestelmät ja suorittamalla
startup-sequencen. DOS on keskeinen osa kaikkea muutakin toimintaa, joten on jo
aika, että tutustumme siihen. DOS tarjoaa ohjelmoijalle funktiot tiedostojen,
hakemistojen ja prosessien käsittelyyn sekä moneen muuhun hommaan. Tässä osassa
selvitän yksinkertaisimpien toimintojen käyttämisen. Aloitamme tiedostojen
käsittelystä, mutta sitä ennen hieman teoriaa.
Massamuistitoiminnot ovat kolmitasoiset. Alimmalla tasolla ovat ajurit, jotka
ohjaavat yksittäisiä tietokoneeseen liitettyjä laitteita. Seuraavalla tasolla on
tiedostojärjestelmä, joka komentaa laiteajureita. Ylimmällä tasolla on DOS, joka
ohjaa tiedostojärjestelmien toimintaa. Ohjelmoija voi käyttää tätä hierarkkista
rakennelmaa helposti puhumalla DOSille. Kaikki alemman tason operaatiot sujuvat
automaattisesti ilman, että ohjelmoijan tarvitsee tietää niistä mitään. Hieman
tarkemmin näistä asioista tarinoin PFS- ja DSP-artikkeleissani numerossa 11.
Ohjelmoija käyttää tiedostoja funktioiden Open(), Close(), Read() jne. välityk-
sellä. Osa funktioista on puskuroituja, minkä ansiosta toiminta on erittäin no-
peaa, vaikka ohjelma lukisi esimerkiksi vain muutaman tavun kerrallaan. Toinen
mahdollisuus on käyttää C-kielen vakiofunktioita, esim. fopen(), fread() tai
open(), read() jne. Minä suosin DOS-funktioiden kutsumista, mutta jälkimmäistä
vaihtoehtoa kannattaa harkita, jos on mahdollisuus, että ohjelma voidaan portata
toiseen käyttöjärjestelmään. Tällöin on syytä käyttää niin vähän käyttöjärjes-
telmäkohtaisia funktioita kuin mahdollista.
{DTiedostojen käsittely
Tiedosto avataan funktiolla Open(). Sille annetaan osoitin tiedoston nimeen ja
moodi, joka on MODE_OLDFILE, jos tiedosto avataan lukemista varten, tai MO-
DE_NEWFILE, jos tiedosto tulee luoda. On olemassa vielä kolmaskin moodi, MO-
DE_READWRITE, joka avaa tiedoston lukemista varten, mutta luo sen, jos sitä ei
ole olemassa. Kirjoittamista varten avattua tiedostoa (MODE_NEWFILE) ei voi ava-
ta toistamiseen, mutta MODE_READWRITE:llä avatun voi. Open() palauttaa osoitti-
men tiedoston kahvaan eli struktuuriin, joka sisältää tietoja siitä. Osoitin on
BCPL-kielinen eli longword-osoitin, joten sitä pitää siirtää kaksi bittiä vasem-
malle, että siitä saa CPU-kelpoisen. Sitä ei kuitenkaan normaalisti tarvitse it-
se käyttää, vaan se annetaan muille funktioille, esim. Read(), niitä
käytettäessä.
Tiedosto suljetaan funktiolla Close(). Sille annetaan Open()-funktiolta saatu
kahva. Tässä on pieni esimerkkikoodi tiedoston avaamisesta ja sulkemisesta:
{BBPTR file; /* Kahva */
{B if( file = Open( "T:Tiedosto", MODE_OLDFILE )) {
... tiedoston käyttöä ...
{B Close(file);
{B } else printf("virhe: eipä ollut tiedostoa\n");
Tästä kehittelemällä voidaan helposti kirjoittaa funktio, joka kertoo, onko
haluttu tiedosto olemassa:
{BBOOL exists( STRPTR name ) {
{BBPTR file;
{B if( file = Open( name, MODE_OLDFILE )) Close(file);
{B return file ? TRUE: FALSE;
{B};
Funktion käyttäminen on helppoa ja samantapaista kuin Rexxissä:
{B if( exists( "s:CopyStuff" )) {
... suorita CopyStuff-skripti ...
{B }
Tämä esimerkki katsoo, onko SYS:S -hakemistossa CopyStuff-nimistä skriptiä ja
jos on, suorittaisi sen. Komentojen (ja skriptien) suorittaminen käsitellään
vielä tässä osassa.
DOSissa on kätevä funktio AddPart(), jonka avulla voidaan liittää toisiinsa ha-
kemistonimiä ja tiedostonimiä. Hakemiston nimihän voi päättyä kaksoispisteeseen
tai kauttaviivaan, tai siinä ei välttämättä ole mitään erityistä merkkiä lopus-
sa. Näin ollen hakemistopolun muodostaminen merkkijonoja kopioimalla on huono
idea. AddPart() osaa käsitellä kaikenlaiset nimet oikein, ja sen avulla on help-
poa yhdistää hakemiston ja tiedoston nimet. Oletetaan, että meillä on puskuri,
jossa on valmiina hakemiston nimi ja siihen pitää liittää tiedostonimi. Se on
helppoa:
{Bchar buf[100] = "DEVS:Monitors";
{B AddPart( buf, "Super72", 100 );
Kutsun jälkeen puskurissa lukee "DEVS:Monitors/Super72". AddPart():lle annetaan
viimeisenä argumenttina käytettävissä olevan tilan suuruus eli puskurin pituus.
On olemassa toinenkin funktio, FilePart(). Se palauttaa osoittimen hakemistopol-
kumerkkijonon viimeiseen elementtiin, joka on tiedostonimi tai hakemisto, jos
polussa ei ole tiedostonimeä. Edellisen jälkeen kutsu "FilePart( buf )" palaut-
taisi siis osoittimen puskuriin kohtaan, jossa on "Super72".
Tiedostoon liittyy osoitin, joka määrää, mihin kohtaan tiedostoon kohdistetut
toiminnot osuvat. Kun tiedosto avataan, osoitin osoittaa tiedoston alkuun. Kun
tiedostosta luetaan dataa, osoitin siirtyy eteenpäin niin paljon kuin on luettu.
Jos luetaan sata tavua, osoitin osoittaa tiedostossa kohtaan 100. Ensimmäinen
kohta on 0 ja suurin yhtä pienempi kuin tiedoston pituus. Kohtaa voidaan muuttaa
kutsumalla funktiota Seek(). Se etsii tiedostosta tietyn kohdan, ja tiedostosta
voidaan lukea halutusta kohdasta. Katso seuraavaa esimerkkiä, niin näet, miten
Seek()-funktiota käytetään.
Tiedostosta luetaan funktiolla Read(). Sille annetaan niin ikään tiedoston kahva
ja sitten osoitin muistialueeseen, johon luettu data sijoitetaan, sekä luetta-
vien tavujen määrä. Tiedostoon kirjoitetaan aivan samalla tavalla, mutta funktio
on Write(). Katsotaanpa:
{BBPTR file; /* Kahva */
{BUBYTE space[100]; /* Muistialue */
{B if( file = Open( "Data", MODE_OLDFILE )) {
/* Read() palauttaa luettujen tavujen lukumäärän. */
{B if( Read( file, space, 100 ) < 100 ))
{B printf("Jotain vikaa. Ei saatu sataa tavua.\n");
/* Haetaan tiedostosta kohta, joka on 300 tavua alusta. */
{B Seek( file, OFFSET_BEGINNING, 300 );
/* Kirjoitetaan sinne. Myös Write() palauttaa tavumäärän. */
{B if( Write( file, space, 50 ) < 50 ))
/* Ei onnistunut. Nyt käytämme hieman kehittyneempää
tekniikkaa. Kysymme DOSilta virhekoodin ja näytämme
käyttäjälle virheilmoituksen. Se saattaa näyttää vaikka
tältä: "error: disk full". */
{B PrintFault(IoErr(),"error");
{B Close(file);
{B } else printf("Ei dataa.\n");
Vielä takaisin Seek()-funktioon. Sille annetaan kahva, moodi ja lukema. Moodille
on kolme vaihtoehtoa. Jos se on kuten esimerkissä eli OFFSET_BEGINNING, lukema
on suoraan uusi osoitin tiedostossa. Moodi voi olla myös OFFSET_CURRENT, jolloin
lukema lisätään nykyiseen osoittimeen. Lukema voi olla myös negatiivinen, jol-
loin paikka siirtyy taaksepäin. Viimeinen vaihtoehto on OFFSET_END, joka on tie-
doston loppu. Tällöin moodin on pakko olla negatiivinen tai nolla, koska tiedos-
ton lopun ohi ei voi mennä. Seek()-funktion avulla voi tätä moodia käyttäen sel-
vittää tiedoston pituuden:
{BULONG FileLength(BPTR file) {
{BULONG currpos;
{B currpos = Seek( file, OFFSET_END, 0 );
{B return Seek( file, OFFSET_BEGINNING, currpos );
{B};
Tämä funktio palauttaa tiedoston pituuden. Seek() toisin kuin vakio-C:n lseek()
palauttaa osoittimen arvon ENNEN muutosta. Siksi tarvitaan kaksi kutsua. Vasta
jälkimmäinen palauttaa edellisen kutsun asettaman paikan eli tiedoston lopun.
Samalla se asettaa edellisen kutsun palauttaman arvon eli edeltäneen paikan ta-
kaisin voimaan.
{9Puskuroidut funktiot
Read() ja Write() eivät ole DOSin puolella puskuroituja funktioita. Pieniä luku-
ja ja kirjoituksia tehtäessä kannattaa käyttää funktioita FGetC() ja FPutC(). Ne
vastaavat vakiofunktioita fgetc() ja fputc() eli niillä voit lukea tai kirjoit-
taa yhden merkin. FGetC():lle annetaan argumenttina vain tiedoston kahva ja
FPutC():lle lisäksi kirjoitettava merkki.
Lisäksi DOSissa on string-versiot eli FGets() ja FPuts(). Ne vastaavat vastaa-
vasti standardeja fgets()- ja fputs()-funktioita. Merkkijono kirjoitetaan tie-
dostoon kutsulla "FPuts( file, "Merkkijono" );". FGets() toimii vastaavasti,
mutta argumetit ovat samat kuin Read():lla. Puskurin lisäksi annetaan siis pus-
kurin pituus, ja saat puskuriin joko dataa LF-merkkiin asti tai niin paljon kuin
siihen mahtuu.
{9Muut operaatiot
Tiedostoille voidaan tehdä vähän muutakin. Kuten äsken kerroin, osa DOSin tie-
dostofunktioista on puskuroituja ja osa ei, joten ongelmia voi seurata, jos
niitä käytetään peräkkäin samaan tiedostoon. Puskuroimaton kirjoitus puskuroidun
kirjoituksen jälkeen voi tapahtua ensin! Puskuroimaton kirjoitus ei välttämättä
aikaansaa välittömästi levyoperaatioita. Data voidaan kirjoittaa puskuriin odot-
tamaan kirjoittamista levylle. Jos haluat pakottaa kirjoittamisen levylle, voit
kutsua funktiota Flush(), niin se tehdään välittömästi:
{B Flush( file );
Tämä on pakollista, kun on esimerkiksi ensin käytetty FPutC()- tai FPuts()-funk-
tiota ja sitten halutaan tehdä Write()-kutsu. DOSissa on myös funktiot tiedosto-
jen tuhoamiselle ja siirtämiselle. Tiedostoa ei avata ennen näitä operaatioita,
vaan niille annetaan nimiä merkkijonoina. Tiedosto tuhotaan näin:
{B DeleteFile( "T:Temppifile" );
Nimenmuutos on yhtä helppoa kuin shellissä:
{B Rename( "T:CurrentStore", "T:OldStore" );
Tämä muuttaa nykyisen tallennustiedoston vanhaksi tiedostoksi. Sekä tuhottava
että uudelleennimettävä kohde voi olla myös hakemisto. Tuhottavan hakemiston
pitää olla tyhjä. Mikäli uudelleennimeämisen lähde on tiedosto ja kohde hakemis-
to, tiedosto siirretään hakemistoon. Jos kohdehakemisto on olemassa ja lähdekin
on hakemisto, on seurauksena virhe. Tiedostoa tai hakemistoa ei voi siirtää toi-
selle levylle. Toiminta on siis melko lailla sama kuin shellissä Rename-komen-
non.
{DInteraktiiviset tiedostot
CON-handlerin avulla voit avata tiedoston, joka onkin ikkuna! Tiedostoon kirjoi-
tettu data tulostuu siihen ja siitä luettu data tulee näppäimistöltä. Tämä on
ehdottomasti kätevin tapa yksinkertaiseen kommunikaatioon käyttäjän kanssa. Kon-
soli-ikkunaa käyttää mm. shell. Konsoli avataan ihan normaalisti Open()-funk-
tiolla ja suljetaan Close()-funktiolla. CON-handler huolehtii ikkunan avaamises-
ta ja sulkemisesta. Voit kyllä toimittaa oman ikkunankin, mutta se on jo hieman
enemmän tarkkuutta vaativa toimenpide, joten siihen en tässä paneudu.
Open()-funktiolle annetaan samanlainen merkkijono kuin esimerkiksi NewShell-ko-
mennolle WINDOW-argumenttina. Konsoli siis on mountattu laitenimellä CON, ja
sitä käytetään avaamisessa:
{B if( con = Open( "CON:0/0/640/256/MyWindow/CLOSE", MODE_OLDFILE )) {
{B } printf("Eipä auennut konsoli, ei.\n");
{6HUOM! Konsoli tulee aina avata MODE_OLDFILE:llä.
Laitenimen jälkeen tulevat ikkunan määritykset, ensin vasemman yläkulman koordi-
naatit ja sen jälkeen ikkunan leveys ja korkeus sekä sen nimi ja optiot, joista
tässä annetaan CLOSE. Se tekee ikkunaan sulkunappulan. Optioilla voidaan ikkuna
avata esimerkiksi ilman reunoja tai tietylle ruudulle. Merkkijono sekä kaikki
käytettävissä olevat optiot on selitetty Using The System Software -kirjassa si-
vuilla 7-38 - 7-40.
DOSissa on kaksi funktiota, joiden avulla voidaan ottaa selvää, onko kyseessä
tietyntyyppinen tiedosto. IsFilesystem() kertoo, onko tiedosto levyllä oleva oi-
kea tiedosto palauttaen TRUE- tai FALSE-statuksen. Funktiolla IsInteractive()
voidaan selvittää, onko tiedosto interaktiivinen eli konsoli. Funktion dokumen-
taatiossa siitä käytetään hienosti termiä virtuaalinen terminaali. Kummallekin
funktiolle annetaan parametrinä kahva tiedostoon.
Jokainen ohjelma saa käynnistyessään valmiina kaksi interaktiivista tiedostoa,
stdin ja stdout. Niihin saa kahvan kutsumalla funktioita Input() ja Output(). Ne
siis palauttavat kahvan ohjelman oletussyöte- ja tulostuskanaviin. Funktiot
eivät ota argumentteja. Interaktiiviseen tiedostoon eli konsoli-ikkunaan tulos-
tetaan kirjoittamalla tiedostoon:
{B Write ( Output(), "Hei.\n", 5 );
Tämä funktiokutsu vastaa tulostusfunktiokutsua:
{B printf("Hei\n" );
Tulostus siis menee tiedostoon, jonka kahvan Output()-funktio palautti, eli ole-
tustulostuskanavaan, joka normaalisti on shell-ikkuna. Sitä tarkoitusta varten
on myös oma funktio, PutStr(). Yllä olevaa vastaa yksinkertainen kutsu "PutStr(
"Hei.\n" );".
Tulostaminen konsoli-ikkunaan tällä tavalla on tietysti hieman hankalaa, joten
kannattaa kirjoittaa funktio sitä varten:
{BULONG Print(BPTR confile, STRPTR string) {
{B Write( confile, string, strlen(string) );
{B};
Nyt tulostaminen konsoli-ikkunaan on helpompaa:
{B Print( con, "Hei\n" );
Ongelmaksi tulee muotoiltujen merkkijonojen tulostaminen. Ratkaisu on merkkijo-
non formatointi kutsumalla RawDoFmt()-funktiota, mutta se on tehtävä konekie-
lellä. Toinen mahdollisuus on käyttää VFPrintf()-funktiota tulostamiseen. Sille
annetaan kahva, merkkijono ja argumenttitaulukko. Se vastaa toiminnaltaan fp-
rintf()-funktiota, mutta sen käyttäminen on erilaista. Argumentteja ei työnnetä
pinoon, vaan niistä muodostetaan edellä mainittu argumenttitaulukko. Tämä on
tietysti hankalaa, joten kannattaa harkita konekielisen rutiinin kirjoittamista,
joka toimisi välikappaleena.
Minulla itselläni eivät ole ollenkaan käytössä kääntäjän tulostusfunktiot. Olen
kirjoittanut omat, jotka kutsuvat edelleen DOSin funktioita. Ne ovat hyvin yk-
sinkertaiset - juuri sellaiset välikappaleet kuin äsken puhuin. Tässä on tarkoi-
tukseen sopivan tulostusfunktion lähdekoodi:
{B_cprintf ; LONG cprintf(file,string[,arg][,arg][...])
{B movem.l d2-d3,-(sp)
{B move.l 12(sp),d1 /* Kahva */
{B move.l 16(sp),d2 /* Merkkijono */
{B lea 20(sp),a0 /* Argumentit */
{B move.l a0,d3
{B Lib VFPrintf,_DOSBase(a4)
{B movem.l (sp)+,d2-d3
{B rts
Koodi tarvitsee near a4 -määrityksen, ja se kääntyy PhxAssilla. Tämä rutiini te-
kee juuri sen, mistä puhuin, eli se on VFPrintf()-funktion stub, joka antaa
funktiolle osoittimen argumenttitaulukkoon. Huomaa, että osoite saadaan lealla.
C-kääntäjä siis luo argumenttitaulukon laittaessaan argumentit pinoon, ja tämä
koodi antaa tulostusfunktiolle osoittimen pinoon siihen kohtaan, jossa argumen-
tit ovat. Kätevää, eikö totta? Nyt voimme tulostaa konsoli-ikkunaan aivan samal-
la tavalla kuin normaaliin oletustulostuskanavaan:
{B cprintf( con, "Hello, %s.\n", "world" );
Jos tulostat ohjelmassa vain yhteen konsoliin, voit muuttaa funktion koodia
niin, että kahva otetaan suoraan siitä:
{B move.l _con(a4),d1
Tällöin sinun ei tarvitse antaa joka kerta kahvaa funktiolle, jolloin ohjelmoin-
ti helpottuu, lähdekoodi lyhenee, ohjelma lyhenee ja sen suoritus nopeutuu. Huo-
maa, että kun poistat yhden argumentin, seuraavat ovat pinossa neljää tavua
aiemmin, eli muuta 16 ja 20 seuraavilla riveillä 12:ksi ja 16:ksi.
Edelleen, jos tulostat vain konsoli-ikkunaasi etkä ollenkaan oletustulostuskana-
vaan, voit muuttaa konekielirutiinin nimen _printf():ksi. Tämä voi kuitenkin ol-
la hieman vaarallista ja sotkea koodiasi tutkailevia ihmisiä tai sinua itseäsi,
joten tee niin varoen. Pidä myös mielessäsi, että tämän jälkeen et voi käyttää
oikeaa printf()-funktiota ja että tämä ei ole varmaankaan kovin "oikeaa" ohjel-
mointia.
Se voi kuitenkin helpottaa ohjelmointia, jos voi käyttää tavalliseen tapaan nor-
maalinnäköistä printf()-funktiota ja unohtaa tyystin, että tulostus menee konso-
li-ikkunaan ja printf() kutsuukin ihan erilaista funktiota kuin tavallisesti. Se
on vain yksi asia vähemmän ohjelmoijan mielessä, jossa niitä yleensä on niin
paljon, että helpotus on tarpeenkin.
{DOhjelman suorittaminen
DOSissa on tehokkaat funktiot lapsiprosessien käynnistämiseen, mutta niihin ei
ole syytä puuttua nyt. Yksinkertainen ja toimiva tapa ajaa ohjelmia on kyllä
mielenkiintoinen. System()-funktiota kutsumalla voimme suorittaa komentoja aivan
kuin ne olisi kirjoitettu shellissä. Käyttäminen on helppoa, kunhan toimitamme
funktiolle pienen taulukon:
{BULONG SysTags[] = {
{B NP_Priority,0,
{B TAG_DONE
{B};
{B System( "Dir", SysTags );
Tämä koodi suorittaa Dir-komennon, jonka tuloste menee ohjelman oletustulostus-
kanavalle eli normaalisti shell-ikkunaan, josta se on käynnistetty. SysTags-tau-
lukkoon voidaan laittaa määrityksiä, jotka ohjaavat komennon suoritusta, mutta
niihin ei ole tarvetta koskea. Voit katsoa niitä dos/dostags.h -tiedostosta.
Toimintoa voidaan vieläkin yksinkertaistaa määrittelemällä makro:
{B#define dos(cmd) System(cmd,SysTags)
Nyt komennon ajaminen on vieläkin helpompaa:
{B dos("Copy DF0:Data/Blah RAM:Use");
Tuloksena on tiedoston kopioituminen ohjelman suorituksen aikana aivan kuin oli-
si kirjoittanut komennon shellissä.
{DMuita DOS-funktioita
DOSissa on paljon funktioita myös yleisiin tarkoituksiin. Eräs niistä on De-
lay(), joka panee prosessin odotustilaan annetuksi ajaksi. Sille annetaan argu-
menttina TICKien lukumäärä, jonka prosessi on nukuksissa. TICKejä on normaalisti
50 sekunnissa, joten esimerkiksi viiden sekunnin tauon ohjelman suoritukseen saa
laittamalla koodiin kutsun "Delay( 250 );". Delay() on erittäin yksinkertainen
tapa pieneen odotteluun.
Käsittelemättä vielä jäävät kokonaan hakemistot. Palaamme kurssin seuraavassa
tai jossakin myöhemmässä osassa DOSiin hakemistojen käsittelyn merkeissä sekä
teemme syvempää luotausta DOSin ympyröihin. Nyt tältä erää näkemiin ja hyvää oh-
jelmoinnillista loppukevättä.